// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package modload

import (
	"bytes"
	"context"
	"encoding/hex"
	"errors"
	"fmt"
	"internal/goroot"
	"io/fs"
	"os"
	"path/filepath"
	"strings"

	"cmd/go/internal/base"
	"cmd/go/internal/cfg"
	"cmd/go/internal/modfetch"
	"cmd/go/internal/modinfo"
	"cmd/go/internal/search"

	"golang.org/x/mod/module"
	"golang.org/x/mod/semver"
)

var (
	infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6")
	infoEnd, _   = hex.DecodeString("f932433186182072008242104116d8f2")
)

func isStandardImportPath(path string) bool {
	return findStandardImportPath(path) != ""
}

func findStandardImportPath(path string) string {
	if path == "" {
		panic("findStandardImportPath called with empty path")
	}
	if search.IsStandardImportPath(path) {
		if goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
			return filepath.Join(cfg.GOROOT, "src", path)
		}
	}
	return ""
}

// PackageModuleInfo返回有关提供
// 一个给定的包。如果模块未启用或包位于
// 标准库或如果包未成功加载
// LoadPackages或ImportFromFiles，返回nil。
func PackageModuleInfo(ctx context.Context, pkgpath string) *modinfo.ModulePublic {
	if isStandardImportPath(pkgpath) || !Enabled() {
		return nil
	}
	m, ok := findModule(loaded, pkgpath)
	if !ok {
		return nil
	}

	rs := LoadModFile(ctx)
	return moduleInfo(ctx, rs, m, 0)
}

func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic {
	if !Enabled() {
		return nil
	}

	if i := strings.Index(path, "@"); i >= 0 {
		m := module.Version{Path: path[:i], Version: path[i+1:]}
		return moduleInfo(ctx, nil, m, 0)
	}

	rs := LoadModFile(ctx)

	var (
		v  string
		ok bool
	)
	if rs.depth == lazy {
		v, ok = rs.rootSelected(path)
	}
	if !ok {
		mg, err := rs.Graph(ctx)
		if err != nil {
			base.Fatalf("go: %v", err)
		}
		v = mg.Selected(path)
	}

	if v == "none" {
		return &modinfo.ModulePublic{
			Path: path,
			Error: &modinfo.ModuleError{
				Err: "module not in current build",
			},
		}
	}

	return moduleInfo(ctx, rs, module.Version{Path: path, Version: v}, 0)
}

// 如果有可用的更新版本，addUpdate将填写m.Update。
func addUpdate(ctx context.Context, m *modinfo.ModulePublic) {
	if m.Version == "" {
		return
	}

	info, err := Query(ctx, m.Path, "upgrade", m.Version, CheckAllowed)
	var noVersionErr *NoMatchingVersionError
	if errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) {
		// 忽略“未找到”和“无匹配版本”错误。
		// 这意味着代理没有匹配的版本或根本没有版本。
		// None
		// 不过，我们应该报告其他错误。控制服务器的攻击者
		// 网络不应该通过干扰来隐藏版本
		// HTTPS连接。控制代理的攻击者仍可能
		// 隐藏版本，因为“列表”和“最新”端点不是
		// 认证的。
		return
	} else if err != nil {
		if m.Error == nil {
			m.Error = &modinfo.ModuleError{Err: err.Error()}
		}
		return
	}

	if semver.Compare(info.Version, m.Version) > 0 {
		m.Update = &modinfo.ModulePublic{
			Path:    m.Path,
			Version: info.Version,
			Time:    &info.Time,
		}
	}
}

// addVersions用已知版本的列表填充m.Versions。
// 排除的版本将被省略。如果listRetracted为false，则为retracted
// 版本也将被省略。
func addVersions(ctx context.Context, m *modinfo.ModulePublic, listRetracted bool) {
	allowed := CheckAllowed
	if listRetracted {
		allowed = CheckExclusions
	}
	var err error
	m.Versions, err = versions(ctx, m.Path, allowed)
	if err != nil && m.Error == nil {
		m.Error = &modinfo.ModuleError{Err: err.Error()}
	}
}

// addRetraction填写m。如果模块由其作者收回，则为Retracted。
// m、 如果加载收回信息时出错，则设置错误。
func addRetraction(ctx context.Context, m *modinfo.ModulePublic) {
	if m.Version == "" {
		return
	}

	err := CheckRetractions(ctx, module.Version{Path: m.Path, Version: m.Version})
	var noVersionErr *NoMatchingVersionError
	var retractErr *ModuleRetractedError
	if err == nil || errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) {
		// 忽略“未找到”和“无匹配版本”错误。
		// 这意味着代理没有匹配的版本或根本没有版本。
		// None
		// 不过，我们应该报告其他错误。控制服务器的攻击者
		// 网络不应该通过干扰来隐藏版本
		// HTTPS连接。控制代理的攻击者仍可能
		// 隐藏版本，因为“列表”和“最新”端点不是
		// 认证的。
		return
	} else if errors.As(err, &retractErr) {
		if len(retractErr.Rationale) == 0 {
			m.Retracted = []string{"retracted by module author"}
		} else {
			m.Retracted = retractErr.Rationale
		}
	} else if m.Error == nil {
		m.Error = &modinfo.ModuleError{Err: err.Error()}
	}
}

// 如果模块已被其用户弃用，则addDeprecation将填写m.Deprecated
// 著者m、 如果加载弃用信息时出错，则会设置错误。
func addDeprecation(ctx context.Context, m *modinfo.ModulePublic) {
	deprecation, err := CheckDeprecation(ctx, module.Version{Path: m.Path, Version: m.Version})
	var noVersionErr *NoMatchingVersionError
	if errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) {
		// 忽略“未找到”和“无匹配版本”错误。
		// 这意味着代理没有匹配的版本或根本没有版本。
		// None
		// 不过，我们应该报告其他错误。控制服务器的攻击者
		// 网络不应该通过干扰来隐藏版本
		// HTTPS连接。控制代理的攻击者仍可能
		// 隐藏版本，因为“列表”和“最新”端点不是
		// 认证的。
		return
	}
	if err != nil {
		if m.Error == nil {
			m.Error = &modinfo.ModuleError{Err: err.Error()}
		}
		return
	}
	m.Deprecated = deprecation
}

// moduleInfo返回从需求中加载的关于模块m的信息
// 在rs中（可能为零，表示m不是从需求中加载的
// 图表）。
func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode ListMode) *modinfo.ModulePublic {
	if m == Target {
		info := &modinfo.ModulePublic{
			Path:    m.Path,
			Version: m.Version,
			Main:    true,
		}
		if v, ok := rawGoVersion.Load(Target); ok {
			info.GoVersion = v.(string)
		} else {
			panic("internal error: GoVersion not set for main module")
		}
		if HasModRoot() {
			info.Dir = ModRoot()
			info.GoMod = ModFilePath()
		}
		return info
	}

	info := &modinfo.ModulePublic{
		Path:     m.Path,
		Version:  m.Version,
		Indirect: rs != nil && !rs.direct[m.Path],
	}
	if v, ok := rawGoVersion.Load(m); ok {
		info.GoVersion = v.(string)
	}

	// completeFromModCache使用模块缓存填充m中的额外字段。
	completeFromModCache := func(m *modinfo.ModulePublic) {
		checksumOk := func(suffix string) bool {
			return rs == nil || m.Version == "" || cfg.BuildMod == "mod" ||
				modfetch.HaveSum(module.Version{Path: m.Path, Version: m.Version + suffix})
		}

		if m.Version != "" {
			if q, err := Query(ctx, m.Path, m.Version, "", nil); err != nil {
				m.Error = &modinfo.ModuleError{Err: err.Error()}
			} else {
				m.Version = q.Version
				m.Time = &q.Time
			}
		}
		mod := module.Version{Path: m.Path, Version: m.Version}

		if m.GoVersion == "" && checksumOk("/go.mod") {
			// 加载go.mod文件以确定go版本，因为它没有
			// 已从rawGoVersion中填充。
			if summary, err := rawGoModSummary(mod); err == nil && summary.goVersion != "" {
				m.GoVersion = summary.goVersion
			}
		}

		if m.Version != "" {
			if checksumOk("/go.mod") {
				gomod, err := modfetch.CachePath(mod, "mod")
				if err == nil {
					if info, err := os.Stat(gomod); err == nil && info.Mode().IsRegular() {
						m.GoMod = gomod
					}
				}
			}
			if checksumOk("") {
				dir, err := modfetch.DownloadDir(mod)
				if err == nil {
					m.Dir = dir
				}
			}

			if mode&ListRetracted != 0 {
				addRetraction(ctx, m)
			}
		}
	}

	if rs == nil {
		// 如果这是“go mod download”的显式版本参数，或
		// “go list-m”，报告实际请求的版本，而不是其替换版本。
		completeFromModCache(info) // 将在供应商模式下设置m.错误。
		return info
	}

	r := Replacement(m)
	if r.Path == "" {
		if cfg.BuildMod == "vendor" {
			// 很容易在“Dir”字段中填入供应商内部的点
			// 目录，但这会产生误导：供应商目录包含
			// 扁平的包树，不是完整的模块，甚至可以
			// 如果一个模块路径是一条路径，则将来自不同模块的包交错放置
			// 另一个的前缀。
		} else {
			completeFromModCache(info)
		}
		return info
	}

	// 不要点击网络为更换的模块填写额外数据。
	// 最初解决的版本和时间并不重要
	// 值得付出代价，我们将从
	// 不管怎么说，都要更换。请参阅https:
	info.Replace = &modinfo.ModulePublic{
		Path:    r.Path,
		Version: r.Version,
	}
	if v, ok := rawGoVersion.Load(m); ok {
		info.Replace.GoVersion = v.(string)
	}
	if r.Version == "" {
		if filepath.IsAbs(r.Path) {
			info.Replace.Dir = r.Path
		} else {
			info.Replace.Dir = filepath.Join(ModRoot(), r.Path)
		}
		info.Replace.GoMod = filepath.Join(info.Replace.Dir, "go.mod")
	}
	if cfg.BuildMod != "vendor" {
		completeFromModCache(info.Replace)
		info.Dir = info.Replace.Dir
		info.GoMod = info.Replace.GoMod
		info.Retracted = info.Replace.Retracted
	}
	info.GoVersion = info.Replace.GoVersion
	return info
}

// PackageBuildInfo返回包含模块版本信息的字符串
// 用于提供按路径和deps命名的包的模块。路径和deps必须
// 命名使用LoadPackages成功解析的包。
func PackageBuildInfo(path string, deps []string) string {
	if isStandardImportPath(path) || !Enabled() {
		return ""
	}

	target := mustFindModule(loaded, path, path)
	mdeps := make(map[module.Version]bool)
	for _, dep := range deps {
		if !isStandardImportPath(dep) {
			mdeps[mustFindModule(loaded, path, dep)] = true
		}
	}
	var mods []module.Version
	delete(mdeps, target)
	for mod := range mdeps {
		mods = append(mods, mod)
	}
	module.Sort(mods)

	var buf bytes.Buffer
	fmt.Fprintf(&buf, "path\t%s\n", path)

	writeEntry := func(token string, m module.Version) {
		mv := m.Version
		if mv == "" {
			mv = "(devel)"
		}
		fmt.Fprintf(&buf, "%s\t%s\t%s", token, m.Path, mv)
		if r := Replacement(m); r.Path == "" {
			fmt.Fprintf(&buf, "\t%s\n", modfetch.Sum(m))
		} else {
			fmt.Fprintf(&buf, "\n=>\t%s\t%s\t%s\n", r.Path, r.Version, modfetch.Sum(r))
		}
	}

	writeEntry("mod", target)
	for _, mod := range mods {
		writeEntry("dep", mod)
	}

	return buf.String()
}

// mustFindModule类似于findModule，但如果
// 找不到模块。
// None
// TODO（jayconrod）：拆下这个。调用方应使用findModule并返回
// 错误，而不是依赖base.Fatalf。
func mustFindModule(ld *loader, target, path string) module.Version {
	pkg, ok := ld.pkgCache.Get(path).(*loadPkg)
	if ok {
		if pkg.err != nil {
			base.Fatalf("build %v: cannot load %v: %v", target, path, pkg.err)
		}
		return pkg.mod
	}

	if path == "command-line-arguments" {
		return Target
	}

	base.Fatalf("build %v: cannot find module for path %v", target, path)
	panic("unreachable")
}

// findModule在路径处搜索包含包的模块。
// 如果已加载包，则返回其包含模块和true。
// 否则，将返回module.Version{}和false。
func findModule(ld *loader, path string) (module.Version, bool) {
	if pkg, ok := ld.pkgCache.Get(path).(*loadPkg); ok {
		return pkg.mod, pkg.mod != module.Version{}
	}
	if path == "command-line-arguments" {
		return Target, true
	}
	return module.Version{}, false
}

func ModInfoProg(info string, isgccgo bool) []byte {
	// 将调试信息作为runtime.modinfo注入变量，
	// 但是在包main中编译它，使其特定于二进制文件。
	// 变量必须是文字，以便具有正确的值
	// 在包主运行的初始值设定项之前。
	// None
	// 运行时启动代码引用变量，使其保持活动状态
	// 在所有二进制文件中。
	// None
	// 注：我们使用下面的gccgo替代配方（基于
	// 初始化函数），因为gccgo不支持
	// 应用一个“
	// 缺点是其他软件包可能希望查看该模块
	// 它们的init函数中的信息（参见第29628期），这不会
	// 为gccgo工作。另见第30344期。

	if !isgccgo {
		return []byte(fmt.Sprintf(`package main
import _ "unsafe"
// go:linkname\uuuu debug\uModInfo\uuuuu runtime.modinfo
var __debug_modinfo__ = %q
`, string(infoStart)+info+string(infoEnd)))
	} else {
		return []byte(fmt.Sprintf(`package main
import _ "unsafe"
// go:linkname\uuuuu set\u debug\u modinfo\uuuuuu runtime.setmodinfo
func __set_debug_modinfo__(string)
func init() { __set_debug_modinfo__(%q) }
`, string(infoStart)+info+string(infoEnd)))
	}
}
